# -*- coding: utf-8 -*-
import json
import os
import time
import datetime
import sys
import socket
import shutil
import logging
import configparser
import urllib.request

import schedule
from minio import S3Error, Minio


logging.basicConfig(level=logging.INFO)

cnf_file = "myback.ini"
config = configparser.ConfigParser()
config.read(cnf_file)

master_host = config.get("master", "master_host")
master_port = config.get("master", "master_port")
master_user = config.get("master", "master_user")
master_password = config.get("master", "master_password")
backup_dir = config.get("myback", "inner_backup_dir")
backup_keep_days = config.get("myback", "backup_keep_days")
cnf_file = "/myback/my.cnf"
backupfilelist = os.path.join(backup_dir, "backupfilelist.log")

minio_host = config.get("minio", "minio_host")
access_key = config.get("minio", "access_key")
secret_key = config.get("minio", "secret_key")
minio_enable = config.get("minio", "minio_enable")
project_code = config.get("minio", "project_code")

on_exectue = config.get("myback", "on_exectue")
start_time = config.get("myback", "start_time")
interval_day = config.get("myback", "interval_day")

back_success_enable = config.get("notice", "back_success_enable")
back_error_enable = config.get("notice", "back_error_enable")
upload_success_enable = config.get("notice", "upload_success_enable")
upload_error_enble = config.get("notice", "upload_error_enble")
notice_subject = config.get("notice", "notice_subject")
wx_to_user = config.get("notice", "wx_to_user")
wx_corpid = 'wwc9d192e8b34c3a6e'
wx_corpsecret = '6owZiKBDJsqu3TZGLNNjD0vk4V8uKpi3Gw1CIeJXrz8'
wx_url = 'https://qyapi.weixin.qq.com'


# 获取备份类型，周日进行完备，平时增量备份，如果没有全备，执行完整备份
def get_backup_type():
    backup_type = None
    if os.path.exists(backupfilelist):
        with open(backupfilelist, 'r') as f:
            lines = f.readlines()
            if (lines):
                last_line = lines[-1]  # get last backup name
                if (last_line):
                    if (time.localtime().tm_wday == 6):
                        backup_type = "full"
                    else:
                        backup_type = "incr"
                else:
                    backup_type = "full"
            else:
                backup_type = "full"
    else:
        open(backupfilelist, "a").close()
        backup_type = "full"
    return backup_type


# 获取最后一次备份信息
def get_last_backup():
    last_backup = None
    if os.path.exists(backupfilelist):
        with open(backupfilelist, 'r') as f:
            lines = f.readlines()
            last_line = lines[-1]  # get last backup name
            if (last_line):
                last_backup = os.path.join(backup_dir, last_line.split("|")[-1])
    return last_backup.replace("\n", "")


# 探测实例端口号
def get_mysqlservice_status():
    mysql_stat = 0
    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    result = s.connect_ex((master_host, int(master_port)))
    # port os open
    if (result == 0):
        mysql_stat = 1
    return mysql_stat


# 清理过期的历史备份信息
def clean_expired_file():
    for backup_name in os.listdir(backup_dir):
        if os.path.isdir(backup_name):
            bak_datetime = datetime.datetime.strptime(backup_name.replace("_full", "").replace("_incr", ""),
                                                      '%Y%m%d%H%M%S')
            if (bak_datetime < datetime.datetime.now() - datetime.timedelta(days=backup_keep_days)):
                shutil.rmtree(os.path.join(backup_dir, backup_name))


# 完整备份
def full_backup(backup_file_name):
    os.system("[ ! -d {0}/{1} ] && mkdir -p {0}/{1}".format(backup_dir, backup_file_name))
    logfile = os.path.join(backup_dir, "{0}/{1}/backuplog.log".format(backup_dir, backup_file_name))
    backup_commond = ''' innobackupex --defaults-file={0} --no-lock {1}/{6}  --user={2} --password={3} --host="{4}" --port={5} --tmpdir={1}/{6} --stream=xbstream --compress  --compress-threads=8 --parallel=4  --extra-lsndir={1}/{6}  > {1}/{6}/{6}.xbstream 2>{7} '''. \
        format(cnf_file, backup_dir, master_user, master_password, master_host, master_port, backup_file_name, logfile)
    execute_result = os.system(backup_commond)
    return execute_result


# 增量备份
def incr_backup(backup_file_name):
    os.system("[ ! -d {0}/{1} ] && mkdir -p {0}/{1}".format(backup_dir, backup_file_name))
    current_backup_dir = "{0}/{1}".format(backup_dir, backup_file_name)
    logfile = os.path.join(backup_dir, "{0}/{1}/backuplog.log".format(backup_dir, backup_file_name))
    # 增量备份基于上一个增量/完整备份
    incremental_basedir = get_last_backup()
    backup_commond = '''innobackupex --defaults-file={0} --no-lock  {6}  --user={2} --password={3} --host={4} --port={5} --stream=xbstream --tmpdir={6} --compress --compress-threads=8 --parallel=4   --extra-lsndir={6} --incremental --incremental-basedir={7} 2> {8} > {6}/{9}.xbstream ''' \
        .format(cnf_file, backup_dir, master_user, master_password, master_host, master_port, current_backup_dir,
                incremental_basedir, logfile, backup_file_name)
    execute_result = os.system(backup_commond)
    return execute_result



def xtrabackup():
    mysql_stat = get_mysqlservice_status()
    backup_type = get_backup_type()
    if mysql_stat <= 0:
        logging.info("master mysql have not started")
        sys.exit(1)
    try:
        start_time = datetime.datetime.now().strftime('%Y%m%d%H%M%S')
        logging.info(datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S') + "--------start backup")
        backup_file_name = start_time
        execute_result = None
        if (backup_type == "full"):
            backup_file_name = backup_file_name + "_full"
            logging.info("execute a full backup......")
            execute_result = full_backup(backup_file_name)
            if (execute_result == 0):
                logging.info(
                    datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S') + "--------Clear history backup start")
                logging.info("execute clear history backup......")
                clean_expired_file()
                logging.info(
                    datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S') + "--------Clear history backup completed")
                # 压缩上传
                compressUpload(backup_file_name)
        else:
            backup_file_name = backup_file_name + "_incr"
            logging.info("execute a incr backup......")
            execute_result = incr_backup(backup_file_name)
        if (execute_result == 0):
            finish_time = datetime.datetime.now().strftime('%Y%m%d%_H%M%S')
            backup_info = start_time + "|" + finish_time + "|" + start_time + "_" + backup_type
            with open(backupfilelist, 'a+') as f:
                f.write(backup_info + '\n')
            logging.info(datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S') + "--------Backup complete!!!")
            #压缩上传
            compressUpload(backup_file_name)
            #备份成功是否通知
            if back_success_enable == '1':
                send_weixin_app(build_msg_content(1,datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S') + "--  {0}.tar.gz  Backup complete!!!".format(backup_file_name)))
        else:
            logging.info(datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S') + "--------Backup failed!!! Please check the log")
            if back_error_enable == '1':
                send_weixin_app(build_msg_content(1,datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S') + "-Backup failed!!! Please check the log"))
        if minio_enable == '1':
            #上传备份清单
            upload_file("backupfilelist.log")
    except:
        raise
        logging.info(datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S') + "--------Backup failed!!!")


def upload_file(file_name):
    minioClient = Minio(
        minio_host,
        access_key,
        secret_key,
        secure=False
    )
    check_bucket = minioClient.bucket_exists("mysqlbackup")
    if not check_bucket:
        minioClient.make_bucket("mysqlbackup")
    try:
        logging.info("start upload file")
        minioClient.fput_object(bucket_name="mysqlbackup", object_name="{0}/{1}".format(project_code,file_name),file_path= "{0}/{1}".format(backup_dir,file_name))
        logging.info("file {0} is successfully uploaded".format(file_name))
        if upload_success_enable == "1":
            send_weixin_app(build_msg_content(2,"file {0} is successfully uploaded".format(file_name)))
    except BaseException as err:
        logging.info("file {0} is failed uploaded".format(file_name))
        print(err)
        if upload_error_enble == "1":
            send_weixin_app(build_msg_content(2,"file {0} is failed uploaded. {1}".format(file_name,err)))

def compressUpload(backup_file_name):
    if minio_enable == '1':
        logging.info("tar compress dir start...")
        os.system("cd {0} && tar -zcvf {1}.tar.gz {2}".format(backup_dir,backup_file_name,backup_file_name))
        logging.info("tar compress dir end...")
        upload_file("{0}.tar.gz".format(backup_file_name))
        time.sleep(1)
        os.system("rm -rf {0}/{1}.tar.gz".format(backup_dir,backup_file_name))
        logging.info("clear tar.gz finlish!!!")
    else:
        logging.info("Minio upload is not enable!!!")


def send_weixin_message(url, token, data):
    send_url = '%s/cgi-bin/message/send?access_token=%s' % (url, token)
    respone = urllib.request.urlopen(urllib.request.Request(url=send_url, data=data)).read()
    x = json.loads(respone.decode())['errcode']
    if x == 0:
        logging.info(datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S') + "-----***-----wechat message push successfully !!!")
    else:
        logging.info(datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S') + "-----***-----wechat message push failed !!!")


def send_weixin_app(msg):
    try:
        token_url = '%s/cgi-bin/gettoken?corpid=%s&corpsecret=%s' % (wx_url, wx_corpid, wx_corpsecret)
        token = json.loads(urllib.request.urlopen(token_url).read().decode())['access_token']

        values = {
            "touser": wx_to_user,
            "msgtype": 'text',
            "agentid": 1000002,
            "text": {'content': msg},
            "safe": 0
        }
        msg_data = (bytes(json.dumps(values), 'utf-8'))
        send_weixin_message(wx_url, token, msg_data)
    except BaseException as e:
         logging.info(datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S') + "-----***-----wechat message push failed !!!")

def build_msg_content(type,msgstr):
    if type == 1:
        msg = "●通知主题：MYSQL物理备份\n●项目代码：{0}\n●通知信息：{1}".format(notice_subject,msgstr)
    else:
        msg = "●通知主题：MYSQL备份文件上传\n●项目代码：{0}\n●通知信息：{1}".format(notice_subject,msgstr)
    return msg


def exec():
    if on_exectue == "1":
        logging.info(datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S') + "-----#####-----execute Now !!!")
        xtrabackup()
    else:
        logging.info(datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S') + "-----#####-----interval {0} day at {1} execute,please  Wait!!!".format(interval_day,start_time))
        if interval_day == "0":
            schedule.every().days.at(start_time).do(xtrabackup)
        else:
            schedule.every(interval_day).days.at(start_time).do(xtrabackup)


if __name__ == '__main__':
    exec()
    while 1:
        time.sleep(1)
        schedule.run_pending()